Skip to content

fix: actionable error for Alembic-blind schema drift#99

Merged
lesnik512 merged 8 commits into
mainfrom
fix/actionable-schema-drift-error
Jun 16, 2026
Merged

fix: actionable error for Alembic-blind schema drift#99
lesnik512 merged 8 commits into
mainfrom
fix/actionable-schema-drift-error

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Summary

OutboxClient.validate_schema() detects schema drift that alembic revision --autogenerate cannot generate a remediation migration for — a missing/altered outbox_lease_ck CHECK constraint and partial-index predicate drift. Previously the raised RuntimeError was a dead end: re-running autogenerate produced an empty upgrade(). Now, when an Alembic-blind drift is present, the error appends a pointer to a new docs section holding the exact hand-written op.* migration recipe.

What changed

  • client.py — new module-level _SCHEMA_MISMATCH_PREFIX / _AUTOGEN_BLIND_HINT and a pure helper _compose_schema_mismatch_message(errors, *, has_blind_drift). validate_schema() collects the two Alembic-blind probes (_validate_index_predicates_sync, _validate_check_constraints_sync) into a blind_errors list and appends the pointer only when one of them fired. The error prefix and per-error strings are byte-for-byte unchanged → existing greps / pytest match= still hold; autogenerate-fixable drift (columns, plain indexes, DLQ) gets no pointer.
  • Docs — new docs/operations/alembic.md#fixing-drift-autogenerate-cant-see section (CHECK + partial-index recipes, with a naming_convention/op.f() caveat) and a cross-link from docs/usage/schema-validation.md.
  • Tests — 3 unit tests for the helper; integration assertions that the pointer fires on CHECK-missing and predicate-wrong drift and is absent on column-missing drift.

Verification

  • just lint-ci clean (ruff format, ruff check, ty, eof-fixer)
  • Full suite: 521 passed, 100% coverage
  • mkdocs build --strict clean

Design + plan: planning/changes/archive/2026-06-16.01-actionable-schema-drift-error/.

🤖 Generated with Claude Code

lesnik512 and others added 8 commits June 16, 2026 19:00
validate_schema() detects CHECK/partial-index drift that
`alembic revision --autogenerate` cannot remediate. Spec appends a
docs-pointer to the RuntimeError (only when blind drift is present) plus a
hand-written-migration recipe in docs/operations/alembic.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…in recipe

Final-review polish: cover the index/check predicate-drift blind path at the
integration level too, and warn operators with a SQLAlchemy naming_convention to
wrap recipe identifiers in op.f().

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…d in #99)

Move the change bundle active → archive with status: shipped / pr: 99, add the
README index entry, and promote the conclusion into CLAUDE.md's User-owned
schema section (validate_schema now appends a remediation pointer for
Alembic-blind drift).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@lesnik512 lesnik512 merged commit 3d7234c into main Jun 16, 2026
3 checks passed
@lesnik512 lesnik512 deleted the fix/actionable-schema-drift-error branch June 16, 2026 16:44
lesnik512 added a commit that referenced this pull request Jun 16, 2026
A MetaData carrying a SQLAlchemy `ck` naming_convention re-templates the
package's explicitly-named lease CheckConstraint (the given name fills the
%(constraint_name)s token), so the live constraint is named e.g.
`ck_<table>_<table>_lease_ck`, not `<table>_lease_ck`. The CHECK probe
hard-coded the literal name, never found the re-templated one, and raised a
spurious "missing CHECK constraint" on a valid schema for every deployment
using the SQLAlchemy/Alembic-recommended convention.

_validate_check_constraints_sync now derives the expected name from the Table
object via _resolve_check_constraint_name (identify the lease constraint by its
normalized predicate, use its convention-resolved .name), falling back to the
literal name only when the table carries no matching constraint. Explicitly
named indexes are unaffected (ix/uq keys only re-template auto-named indexes).

- tests: convention-resolved name honored, missing-under-convention, literal
  fallback (unit), and an end-to-end Postgres pass under a ck convention.
- docs/operations/alembic.md: replace the misleading op.f(literal) caveat with
  an introspect-the-rendered-name recipe.
- architecture/dlq.md: record the convention-awareness invariant.
- planning/releases/0.10.2.md: release notes.
- planning: remove stale draft duplicate of the shipped #99 bundle from active/
  (canonical copy already in archive/).

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant